
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <math.h>
#include <amanita/aString.h>
#include <amanita/aHashtable.h>
#include <amanita/aRandom.h>
#include "../Main.h"
#include "../TextPage.h"
#include "TarotDivinationPage.h"


int TarotDivinationPage::colors[6] = {0xffffff,0xffe066,0x990000,0x660000,0x330000,0x000000};
int TarotDivinationPage::element_colors[5] = { 0xff0000,0x0000ff,0xffff00,0x00cc00,0xffffff };


void TarotDivinationPage::webview_hover_event_callback(WebKitWebView *webview,gchar *title,gchar *uri,gpointer data) {
	app.handleAction(ACTION_SHOW_STATUS,0,(void *)(uri? uri : aString::blank));
}

gboolean TarotDivinationPage::webview_navigation_decision_event_callback(WebKitWebView *webview,WebKitWebFrame *frame,WebKitNetworkRequest *request,
																	WebKitWebNavigationAction *action,WebKitWebPolicyDecision *decision,gpointer data) {
	int reason = webkit_web_navigation_action_get_reason(action);
	if(reason>=WEBKIT_WEB_NAVIGATION_REASON_LINK_CLICKED && reason<=WEBKIT_WEB_NAVIGATION_REASON_FORM_RESUBMITTED) {
//		const gchar *uri = webkit_web_navigation_action_get_original_uri(action);
		const gchar *uri = webkit_network_request_get_uri(request);
app.printf("TarotDivinationPage::webview_navigation_decision_event_callback(uri=\"%s\",reason=%d)",uri,reason);
		webkit_web_policy_decision_ignore(decision);
		new TextPage(uri);
		app.handleAction(ACTION_SHOW_STATUS,0,(void *)aString::blank);
		return TRUE;
	} else return FALSE;
}

/*gboolean TarotDivinationPage::webview_navigation_event_callback(WebKitWebView *webview,WebKitWebFrame *frame,WebKitNetworkRequest *request,gpointer data) {
	const gchar *uri = webkit_network_request_get_uri(request);
app.printf("TarotDivinationPage::webview_navigation_event_callback(uri=\"%s\")",uri);
	new TextPage(uri);
	app.handleAction(ACTION_SHOW_STATUS,0,(void *)aString::blank);
	return TRUE;
}*/

gboolean TarotDivinationPage::table_configure_event_callback(GtkWidget *widget,GdkEventConfigure *event,gpointer data) {
	TarotDivinationPage &page = *(TarotDivinationPage *)data;
	page.arrangeCards(0,0,gtk_widget_get_allocated_width(widget),gtk_widget_get_allocated_height(widget));
	return TRUE;
}

gboolean TarotDivinationPage::table_expose_event_callback(GtkWidget *widget,GdkEventExpose *event,gpointer data) {
	TarotDivinationPage &page = *(TarotDivinationPage *)data;
	page.paint(event->region);
	return TRUE;
}

gboolean TarotDivinationPage::table_mouse_move_event_callback(GtkWidget *widget,GdkEventMotion *event,gpointer data) {
	TarotDivinationPage &page = *(TarotDivinationPage *)data;
	int x = aMath::round(event->x),y = aMath::round(event->y);
	if(page.mouse.pressed) {
		page.mouse.dragged = true;
		page.mouseMove(x,y,&page.mouse.drag,event);
		page.mouse.drag.x = x,page.mouse.drag.y = y;
	} else {
		page.mouseMove(x,y,0,event);
	}
	return TRUE;
}

gboolean TarotDivinationPage::table_mouse_press_event_callback(GtkWidget *widget,GdkEventButton *event,gpointer data) {
	TarotDivinationPage &page = *(TarotDivinationPage *)data;
app.printf("TarotDivinationPage::table_mouse_press_event_callback(button=%d,type=%d)",event->button,event->type);
	int x = aMath::round(event->x),y = aMath::round(event->y);
	if(event->button==1) {
		page.mouse.pressed = true;
		page.mouse.dragged = false;
		page.mouse.drag.x = x;
		page.mouse.drag.y = y;
	}
	page.mouseDown(x,y,event);
	return TRUE;
}

gboolean TarotDivinationPage::table_mouse_release_event_callback(GtkWidget *widget,GdkEventButton *event,gpointer data) {
	TarotDivinationPage &page = *(TarotDivinationPage *)data;
app.printf("TarotDivinationPage::table_mouse_press_event_callback(button=%d,type=%d)",event->button,event->type);
	int x = aMath::round(event->x),y = aMath::round(event->y);
	if(page.mouse.dragged) page.mouseUp(x,y,event);
	page.mouse.pressed = false;
	page.mouse.dragged = false;
	return TRUE;
}

TarotDivinationPage::TarotDivinationPage(Profile *p,const char *d,const char *s,int sp) : Page(),profile(),deck(),cards(),tableau(),star(deck),patterns() {
	if(p) profile.setProfile(*p);
	else profile.setName("Tarot Divination");
	selected = 0;
	table.view = 1;
	table.spread = sp;

	FILE *fp = app.fopenlangf(0,"tarot/patterns.txt");
	patterns.load(fp);
	fclose(fp);

	deck.load(d);
	deck.loadImages(d);
	cards = deck;
	star.load(s);

	switch(table.spread) {
		case 0:
			cards.setCardState(CARD_BACK|CARD_MOBILE|CARD_HIDE_REVERSED,true);
			cards.mix(rnd,0,deck.getReversed());
			tableau = cards;
			break;

		case 1:
			cards.mix(rnd,0,deck.getReversed());
			tableau = cards;
			for(int i=0; i<star.size(); i++) star.pickCard(*tableau.getCard(0));
			tableau.setVisible(false);
			star.analyze();
			table.view = 2;
			break;
		
		case 2:
			cards.setCardState(CARD_MOBILE,true);
			tableau = cards;
			break;
	}

	aString str;
	writeStarText(str,star);

	GtkWidget *page = gtk_table_new(2,1,FALSE);

	GtkWidget *scroll = gtk_scrolled_window_new(NULL,NULL);
	gtk_widget_set_size_request(scroll,360,-1);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),GTK_POLICY_AUTOMATIC,GTK_POLICY_ALWAYS);
	//gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),GTK_SHADOW_IN);
	gtk.webview = webkit_web_view_new();
	WebKitWebSettings *settings = webkit_web_settings_new();
	g_object_set(settings,
			"auto-load-images",TRUE,
			"auto-shrink-images",FALSE,
			"default-encoding","UTF-8",
			"enable-plugins",FALSE,
		NULL);
	webkit_web_view_set_settings(WEBKIT_WEB_VIEW(gtk.webview),settings);
	char base[256];
	sprintf(base,"file://%s",app.getHomeDir());
	webkit_web_view_load_string(WEBKIT_WEB_VIEW(gtk.webview),str.toCharArray(),"text/html","UTF-8",base);
	g_signal_connect(G_OBJECT(gtk.webview),"hovering-over-link",G_CALLBACK(webview_hover_event_callback),this);
	g_signal_connect(G_OBJECT(gtk.webview),"navigation-policy-decision-requested",G_CALLBACK(webview_navigation_decision_event_callback),this);
//	g_signal_connect(G_OBJECT(gtk.webview),"navigation-requested",G_CALLBACK(webview_navigation_event_callback),this);
	gtk_container_add(GTK_CONTAINER(scroll),gtk.webview);
	gtk_table_attach(GTK_TABLE(page),scroll,0,1,0,1,
		(GtkAttachOptions)(GTK_FILL|GTK_SHRINK),(GtkAttachOptions)(GTK_FILL|GTK_EXPAND),2,2);

	gtk.table = gtk_drawing_area_new();
	g_signal_connect(G_OBJECT(gtk.table),"configure_event",G_CALLBACK(table_configure_event_callback),this);
	g_signal_connect(G_OBJECT(gtk.table),"expose_event",G_CALLBACK(table_expose_event_callback),this);
	g_signal_connect(G_OBJECT(gtk.table),"motion_notify_event",G_CALLBACK(table_mouse_move_event_callback),this);
	g_signal_connect(G_OBJECT(gtk.table),"button_press_event",G_CALLBACK(table_mouse_press_event_callback),this);
	g_signal_connect(G_OBJECT(gtk.table),"button_release_event",G_CALLBACK(table_mouse_release_event_callback),this);
	gtk_widget_set_events(gtk.table,GDK_EXPOSURE_MASK
			| GDK_LEAVE_NOTIFY_MASK
			| GDK_BUTTON_PRESS_MASK
			| GDK_BUTTON_RELEASE_MASK
			| GDK_POINTER_MOTION_MASK
			| GDK_POINTER_MOTION_HINT_MASK);
	gtk_table_attach(GTK_TABLE(page),gtk.table,1,2,0,1,
		(GtkAttachOptions)(GTK_FILL|GTK_EXPAND),(GtkAttachOptions)(GTK_FILL|GTK_EXPAND),2,2);

	Page::open(profile.name(),page);

fp = fopen("index.html","w");
fprintf(fp,"%s",str.toCharArray());
fclose(fp);
}

TarotDivinationPage::~TarotDivinationPage() {
}

void TarotDivinationPage::paint(const cairo_region_t *region) {
/*
//	int i,n;
	cairo_t *cr = gdk_cairo_create(gtk.table->window);
	if(region) {
		gdk_cairo_region(cr,region);
		cairo_clip(cr);
	}
	cairo_scale(cr,table.scale,table.scale);
	setColor(cr,0x000066);
	GdkRectangle *rects = 0;
	int nrects = 0;
	if(region) {
//		gdk_cairo_region(cr,region);
//		cairo_clip(cr);
//		gdk_cairo_region(cr,region);
		gdk_region_get_rectangles(region,&rects,&nrects);
		if(table.scale!=1.0) for(int i=0; i<nrects; i++) {
			GdkRectangle &r = rects[i];
			r.x = scaleFrom(r.x);
			r.y = scaleFrom(r.y);
			r.width = scaleFrom(r.width);
			r.height = scaleFrom(r.height);
			cairo_rectangle(cr,r.x-1,r.y-1,r.width+2,r.height+2);
			cairo_fill(cr);
		} else {
			gdk_cairo_region(cr,region);
			cairo_fill(cr);
		}
	} else {
		cairo_rectangle(cr,0,0,table.dimension.width,table.dimension.height);
		cairo_fill(cr);
	}
	cairo_stroke_preserve(cr);

	for(int i=0; i<table.nareas; i++) {
		GdkRectangle &r = table.areas[i];
		Page::setColor(cr,0x000099);
		cairo_rectangle(cr,r.x,r.y,r.width,r.height);
		cairo_stroke_preserve(cr);
		Page::setColor(cr,0x000033);
		cairo_fill(cr);
	}

	switch(table.view) {
		case 1:
			star.paint(cr,rects,nrects);
			tableau.paint(cr,rects,nrects);
			break;
		case 2:
			star.paint(cr,rects,nrects);
			break;
	}

	cairo_stroke(cr);
	cairo_destroy(cr);
*/
}

void TarotDivinationPage::repaint(const GdkRectangle *rects,int nrects) {
/*
	if(!rects || !nrects) gdk_window_invalidate_rect(gtk.table->window,NULL,FALSE);
	else {
		GdkRectangle r;
		for(int i=0; i<nrects; i++) {
			r = rects[i];
			if(table.scale!=1.0) {
				r.x = scaleTo(r.x);
				r.y = scaleTo(r.y);
				r.width = scaleTo(r.width);
				r.height = scaleTo(r.height);
			}
			gdk_window_invalidate_rect(gtk.table->window,&r,FALSE);
		}
	}
*/
}


//void TarotDivinationPage::paintLabel(cairo_t *cr,Card &c) {
//	if(!&c || !c.isVisible()) return;
//	GdkPixbuf &img = c.image();
//	double p = (double)c.width/(double)img.hw;
//	int n = c.getId(),w = aMath::round((double)labelImage->hw*p),h = aMath::round((double)labelImage->hh*p);
//	drawImage(*labelImage,c.x-5,c.y-2,w,h,labelImage->hw*(n-1),0,labelImage->hw,labelImage->hh);
//}

void TarotDivinationPage::calculateScale(int &w,int &h,double sw,double sh) {
	double nw = w,nh = h;
	if(nw<sw || nh<sh) {
		sw = nw/sw;
		sh = nh/sh;
		table.scale = sw<sh? sw : sh;
		w = scaleFrom(w);
		h = scaleFrom(h);
	} else table.scale = 1.0;
}

GdkRectangle &TarotDivinationPage::addTableArea(int x,int y,int w,int h) {
	GdkRectangle &r = table.areas[table.nareas++];
	r.x = x,r.y = y,r.width = w,r.height = h;
	return r;
}

void TarotDivinationPage::arrangeCards(int x,int y,int w,int h) {
	const int spacing = 10; // Padding between spread areas.
	int sw = star.getStarWidth();
	int sh = star.getStarHeight();

	resetTable();
	switch(table.view) {
		case 1:
		{
			switch(table.spread) {
				case 0:
				{
					calculateScale(w,h,w,sh+spacing*3+aMath::round((double)deck.getHeight()*1.2));
					sw = w-spacing*2;
					sh = h-spacing*3-aMath::round((double)deck.getHeight()*1.2);
					cards.spreadArch(addTableArea(x+spacing,y+sh+spacing*2,w-spacing*2,h-sh-spacing*3));
					break;
				}
				case 1:
					calculateScale(w,h,sw+spacing*2,sh+spacing*2);
					sw = w-spacing*2,sh = h-spacing*2;
					break;
				case 2:
				{
					calculateScale(w,h,
						aMath::max(spacing*2+(deck.getWidth()+2)*14-2,spacing*3+(deck.getWidth()+2)*7-2+sw),
						spacing*3+sh+(deck.getHeight()+spacing)*4-spacing);
					sh = aMath::max(h-(spacing*3+(deck.getHeight()+spacing)*4-spacing),sh);
					cards.spreadSorted(addTableArea(x+spacing,y+spacing,w-sw-spacing*3,sh),2,TAROT_TRUMP_0,TAROT_TRUMP_XXI);
					for(int i=0; i<4; i++) deck.spreadSorted(
								addTableArea(x+spacing,y+sh+spacing*2+(deck.getHeight()+spacing)*i,w-spacing*2,deck.getHeight()),
								2,TAROT_FIRE_ACE+i*14,TAROT_FIRE_COURT4+i*14);
					break;
				}
			}
			star.placeCards(addTableArea(w-spacing-sw,y+spacing,sw,sh));
			break;
		}
		case 2:
			calculateScale(w,h,sw+spacing*2,sh+spacing*2);
			star.placeCards(addTableArea(x+spacing,y+spacing,w-spacing*2,h-spacing*2));
			break;
	}
	table.dimension.x = x;
	table.dimension.y = y;
	table.dimension.width = w;
	table.dimension.height = h;
}

int TarotDivinationPage::mouseMove(int x,int y,GdkPoint *drag,GdkEventMotion *event) {
	if(table.scale!=1.0) x = scaleFrom(x),y = scaleFrom(y);
	if(drag) {
		if(!selected) return -1;
		else if(selected->isMobile()) {
			GdkRectangle r[2];
			int dx = drag->x,dy = drag->y;
			if(table.scale!=1.0) dx = scaleFrom(dx),dy = scaleFrom(dy);
			selected->getBounds(r[0]);
			selected->setLocation(selected->getX()+x-dx,selected->getY()+y-dy);
			selected->getBounds(r[1]);
			repaint(r,2);
		}
	}
	return 1;
}

int TarotDivinationPage::mouseDown(int x,int y,GdkEventButton *event) {
	if(table.scale!=1.0) x = scaleFrom(x),y = scaleFrom(y);
	Card *c = findCard(x,y);
app.printf("TarotDivinationPage::mouseDown(x=%d,y=%d,card=%s,button=%d)",x,y,c? c->getName() : "none",event->button);
	int n = 0;
	GdkRectangle r[3];
	if(selected) selected->getBounds(r[n++]);
	if(c) {
		selectCard(c);
		if(selected->isMobile()) selected->getCardSet().moveToTop(*selected);
		selected->getBounds(r[n++]);
		if(event->button==3) c->setReversed(!c->isReversed());
	} else if(selected && event->button==1) {
		int i = star.findCardIndex(x,y);
		if(i>=0) {
			c = star.getCard(i);
			star.pickCard(*selected,i);
			if(star.isFull()) {
				starSelected();
				return 1;
			} else {
				selected->getBounds(r[n++]);
				if(c) c->getBounds(r[n++]);
			}
		} else selectCard(0);
	} else return -1;
	if(n) repaint(r,n);
	return 1;
}

int TarotDivinationPage::mouseUp(int x,int y,GdkEventButton *event) {
	if(table.scale!=1.0) x = scaleFrom(x),y = scaleFrom(y);
	if(event->button==1) {
app.printf("TarotDivinationPage::mouseUp(x=%d,y=%d)",x,y);
		if(selected) {
			int i = star.findCardIndex(x,y);
			if(i>=0) {
				int n = 0;
				GdkRectangle r[3];
				Card *c = star.getCard(i);
				if(c==selected) return 1;
				selected->getBounds(r[n++]);
				star.pickCard(*selected,i);
				if(star.isFull()) {
					starSelected();
					return 1;
				} else {
					selected->getBounds(r[n++]);
					if(c) c->getBounds(r[n++]);
				}
				if(n) repaint(r,n);
			}
		} else return -1;
	}
	return 1;
}

Card *TarotDivinationPage::findCard(int x,int y) {
	Card *c = 0;
	if(tableau.isVisible()) for(int i=tableau.size()-1; i>=0; i--) {
		c = tableau.getCard(i);
		if(c && c->contains(x,y)) return c;
	}
	if(star.isVisible()) for(int i=star.size()-1; i>=0; i--) {
		c = star.getCard(i);
		if(c && c->contains(x,y)) return c;
	}
	return 0;
}

void TarotDivinationPage::selectCard(Card *c,bool unselect_previous) {
	if(selected) {
		if(unselect_previous) selected->setSelected(false);
		if(selected->isBack()) selected = 0;
	}
	if(c && c->isFront()) {
		if(c!=selected) showCardText(*c);
	} else if(selected) showStarText(star);
	selected = c;
	if(selected) selected->setSelected(true);
}

void TarotDivinationPage::starSelected() {
	tableau.setVisible(false);
	star.analyze();
	table.view = 2;
	arrangeCards(0,0,gtk_widget_get_allocated_width(gtk.table),gtk_widget_get_allocated_height(gtk.table));
	repaint();
	showStarText(star);
}

void TarotDivinationPage::showText(aString &str) {
	char base[256];
	sprintf(base,"file://%s",app.getHomeDir());
	webkit_web_view_load_string(WEBKIT_WEB_VIEW(gtk.webview),str.toCharArray(),"text/html","UTF-8",base);
}

void TarotDivinationPage::showStarText(TarotStar &s) {
	aString str;
	writeStarText(str,s);
	showText(str);
}

void TarotDivinationPage::showCardText(Card &c) {
	aString str;
	writeCardText(str,c);
	showText(str);
}

void TarotDivinationPage::writeStarText(aString &str,TarotStar &s) {
	int i,n,g,sum;
	Card *c;
	str.append("<html>\n"
		"<head>\n"
		"<link type=\"text/css\" rel=\"stylesheet\" href=\"style.css\" />\n"
		"<link type=\"text/css\" rel=\"stylesheet\" href=\"style.tarot.css\" />\n"
		"<script src=\"script.js\"></script>\n"
		"</head>\n\n"
		"<body>\n");
	str.append("<h3>").append(star.getName()).append(":</h3><ol>\n");
	for(i=0; i<star.size(); i++) {
		str.append("<li><b>").append(star.getCardName(i)).append(":</b> <i>").append(star.getCardTitle(i)).append("</i>");
		if((c=star.getCard(i))) str.append("<br>").append(c->getName());
		str.append("</li>\n");
	}
	str.append("</ol>\n");
	if(star.isFull()) {
		for(i=0,sum=0; i<4; i++) sum += star.getElement(i);
		if(sum>0) {
			str.append("<p><h5>").append("Element Fördelning").append(":</h5>\n");
			str.append("<table width=\"100%\" cellspacing=\"0\" cellpadding=\"0\">\n\t<tr>");
			char e[8];
			for(i=0; i<4; i++) {
				sprintf(e,"%.2lf%%",(star.getElement(i)*100.0)/(double)sum);
				str.append("\t<td class=\"element").append(i+1).append("\"><b>").append(app.occult.elementName(i,OCCULT_TAROT)).
					append(":</b> ").append(e).append("</td>\n");
				if(i==1) str.append("\t</tr>\n\t<tr>\n");
			}
			str.append("\t<tr>\n</table>\n</p>\n");
			if(star.edMax()!=star.edMin() && (star.edMin()<0 || (star.edMax()>>1)>star.edMin())) {
				str.append("<p><h5>").append("Starka").
					append(' ').append("Kort").append(" (").append(star.edMax()).append(")").append(":</h5><br>\n");
				for(i=0,n=0; i<star.size(); i++) if(star.ed(i)==star.edMax()) {
					if(n++) str.append("<br>\n");
					str.append("<b>").append(i+1).append(".</b> ").append(star.getCard(i)->getName());
				}
				str.append("</p>\n<p><h5>").append(star.edMin()>=0? "Svaga" : "Negativa").
					append(' ').append("Kort").append(" (").append(star.edMin()).append(")").append(":</h5><br>\n");
				for(i=0,n=0; i<star.size(); i++) if(star.ed(i)==star.edMin()) {
					if(n++) str.append("<br>\n");
					str.append("<b>").append(i+1).append(".</b> ").append(star.getCard(i)->getName());
				}
				str.append("</p>\n");
			} else str.append("<p>").
				append("Läggningen är väl balanserad och saknar framträdande starka eller svaga kort.").append("</p>\n");

			for(i=1,n=0; i<=14; i++) if((g=star.getGroup(i))>=(i==1? 2 : 3)) {
				if(n++) str.append("<br>\n");
				else str.append("<p><h5>").append("Grupperingar").append(":</h5><br>\n");
				str.append(g==2? "Par" : (g==3? "Triss" : "Fytal")).append(" i ");
				str.append(app.occult.minorArcanaName(i)).append(" - ").append(patterns[(i-1)*3+(g-2)]);
			}
			if(n) str.append("</p>\n");

//			str.append("{\\pard\\sb100\\li100\\ri100 (");
//			for(i=0; i<star.size(); i++) str.append(star.ed(i)).append(i==star.size()-1? ')' : ',');
//			str.append("\\line max=").append(star.edMax()).append("\\line min=").append(star.edMin()).append("\\par}\n");
		}
	}
	str.append("</body>\n</html>\n");
}

void TarotDivinationPage::writeCardText(aString &str,Card &c) {
	str.append("<html>\n"
		"<head>\n"
		"<link type=\"text/css\" rel=\"stylesheet\" href=\"style.css\" />\n"
		"<link type=\"text/css\" rel=\"stylesheet\" href=\"style.tarot.css\" />\n"
		"<script src=\"script.js\"></script>\n"
		"</head>\n\n"
		"<body>\n");
	str.append("<p><h3>").append(c.getName()).append("</h3><br>\n<h5>").append(app.occult.arcanaName(c.getArcana())).
		append("<h5><br>\n<ul>\n\t<li><b>").append("Element").append(":</b> ").
		append("<span class=\"symbol").append((int)app.occult.elementToSymbol(c.getElement(),OCCULT_TAROT)).append(" tarot1\">").
		append(app.occult.symbolToChar(app.occult.elementToSymbol(c.getElement(),OCCULT_TAROT))).append("</span> ").
		append(app.occult.symbol(app.occult.elementToSymbol(c.getElement(),OCCULT_TAROT))).append("</li>\n\t<li><b>").
		append("Astrologi").append(":</b> ");

	char c1 = c.primarySymbol(),c2 = c.secondarySymbol();
	int s1 = app.occult.charToSymbol(c1),s2 = app.occult.charToSymbol(c2);
	if(c2==' ' || c.isMajor())
		str.append("<span class=\"symbol").append((int)s1).append(" tarot1\">").
			append(c1).append("</span> ").append(app.occult.symbol(s1));
	else {
		str.append("<span class=\"symbol").append((int)s1).append(" tarot1\">").append(c1).
			append("</span><span class=\"element").append(app.occult.symbolElement(s2)).append(" tarot1\">").append(c2).
			append("</span> ").append(app.occult.symbol(s1)).append(' ').
			append(c.isCourt()? "av" : "i").append(' ').append(app.occult.symbol(s2));
	}
	str.append("</li>\n");
	if(c.isMajor()) {
		qabalah_hebrew h = deck.trumpToHebrew(deck.symbolToTrump(s2));
		str.append("\t<li><b>").append("Hebreiska").append(":</b> ").append("<span class=\"tarot1\">").
			append(Qabalah::hebrewToChar(h)).append("</span> ").append(app.occult.hebrewLetterName(h)).append("</li>\n");
	}
	str.append("</ul>\n</p></body>\n</html>\n");
}

